#pragma once
// 封装一个环形队列
// 当环形队列push一个数据是相当于生产者生产产品
// 当环形队列pop一个数据是相当于消费者消费产品
#include <iostream>
#include <queue>
#include <mutex>
#include <pthread.h>
#include "lockGuard.hpp"

const int gefaultCap = 5;

template <class T>
class BlockQueue
{
private:
    bool isQueueEmpty()
    {
        return bq_.size() == 0;
    }
    bool isQueueFull()
    {
        return bq_.size() == capacity_;
    }

public:
    BlockQueue(int capacity = gefaultCap) : capacity_(capacity)
    {
        pthread_mutex_init(&mtx_, nullptr);  // 初始化锁
        pthread_cond_init(&Empty_, nullptr); // 初始化条件变量（判空）
        pthread_cond_init(&Full_, nullptr);  // 初始化条件变量（判满）
    }

    void push(const T &in)
    {
        // pthread_mutes_lock(&mtx_);
        // 1、先检测当前的临界资源是否能够满足访问条件（判满/空）
        // pthread_cond_wait : 我们在临界区中！ 我们是持有锁的， 如果我们去等待了，那么锁该怎么办
        // pthread_cond_wait : 第二个参数是一个锁，当成功调用之后，传入的锁会被自动释放

        // 当我被唤醒是，我是从哪里醒来的呢？？？
        // 从哪里阻塞挂起，就从哪里来 ---被唤醒的时候我们还是在临界区。
        // 当我被唤醒时，pthread_cond_wait 会自动帮我这个线程获取锁

        // pthread_cond_wait : 只是一个函数，有可能调用失败
        // pthread_cond_wait : 就可能存在伪唤醒

        // 所以这里我们不能用if去判断临界资源是否可以访问，我们必须用while循环来判断

        lockGuard lockgrard(&mtx_);
        while (isQueueFull())
        {
            // 2、访问临界资源，100%确定，起源就是就绪的
            pthread_cond_wait(&Full_, &mtx_);
        }
        bq_.push(in);
        pthread_cond_signal(&Empty_);  //唤醒正在因为队列空而等待的线程

        //线程 A 可能在检查某个条件（例如，一个队列是否为空）后，调用 pthread_cond_wait(&Empty_, &mutex); 来等待该条件成立。
        //在调用 pthread_cond_wait 时，线程 A 会释放它持有的互斥锁 mutex，并进入等待状态，直到另一个线程发送一个信号。
        //当线程 B 改变了条件（例如，它往队列中添加了一个元素，使得队列不再为空），线程 B 可以调用 pthread_cond_signal(&Empty_); 来发送一个信号。
        //收到信号的线程 A（或者任何其他正在等待 Empty_ 的线程）会被唤醒，并重新获取 mutex，然后可以继续执行。
    } // 自动调用lockgrard的析构函数

    void pop(T *out)
    {
        lockGuard lockguard(&mtx_);
        while (isQueueEmpty())
        {
            pthread_cond_wait(&Empty_, &mtx_);
        }
        *out = bq_.front();
        bq_.pop();
        pthread_cond_signal(&Full_);//唤醒正在因为队列满而等待的线程
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&mtx_);
        pthread_cond_destroy(&Empty_);
        pthread_cond_destroy(&Full_);
    }

private:
    queue<T> bq_;          // 环形队列
    int capacity_;         // 队列的容量
    pthread_mutex_t mtx_;  // 锁
    pthread_cond_t Empty_; // 条件变量（判空）
    pthread_cond_t Full_;  // 条件变量（判满）
};
